package org.flixel
{	
	import flash.utils.Dictionary;
	/**
	 * ...
	 * @author Kronoshifter
	 */
	public class FlxTimer
	{
		private static const VERSION:Number = 1.2;
 
		public static const COMPLETE:int = 0;
		public static const START:int = 1;
 
		private var _duration:Number;
		private var _currentDuration:Number;
		private var _running:Boolean;
		private var _listeners:Vector.<Function>;
		private var _listenersParams:Dictionary;
		private var _listenerType:Dictionary;
		private var _onceListeners:Dictionary;
		private var _repeat:int;
		private var _currentRepeat:int;
 
 
		/**
		 * The timer's current state; true if the timer is running, otherwise false.
		 */
		public function get running():Boolean 
		{
			return _running;
		}
 
		/**
		 * How long the timer will count before dispatching, in seconds.
		 */
		public function get duration():Number 
		{ 
			return _duration; 
		}
 
		/**
		 * How long the timer will count before dispatching, in seconds.
		 */
		public function set duration(value:Number):void 
		{
			_duration = value;
			stop();
		}
		
		public function addToDuration(value:Number):void
		{
			_currentDuration += value * 2;
		}
		
		public function setDuration(value:Number):void 
		{
			_duration = value * 2;
			stop();
		}
 
		/**
		 * The total duration of the timer, incleading repeats.
		 */
		public function get totalDuration():Number
		{
			return (_repeat < 0) ? _duration : (1 + _repeat) * _duration;
		}
 
		/**
		 * The current progress of this cycle of the timer. 0 is no progress, 0.5 is halfway, and 1 is finished.
		 */
		public function get progress():Number
		{
			return 1 - (_currentDuration / _duration);
		}
 
		/**
		 * The progress overall of the timer. Includes all cycles.
		 */
		public function get totalProgress():Number
		{
			return (_repeat < 0) ? progress : (1 - (_currentRepeat / _repeat)) + progress;
		}
 
		/**
		 * The number of times this timer repeats. -1 repeats infinitely.
		 */
		public function get repeat():int
		{
			return _repeat;
		}
 
		/**
		 * The number of times this timer repeats. -1 repeats infinitely. Setting the repeat will reset the timer.
		 */
		public function set repeat(value:int):void
		{
			_repeat = value;
			reset();
		}
 
		/**
		 * Creates a FlxTimer object. Call add() to add a method to be called by the timer.
		 * The timer will add itself to the internal update loop, and it'll remove itself when destroy() is called.
		 * 
		 * @param	durations	The durations, in seconds, of the timer.
		 * @param	repeat		The number of times the timer will repeat before stopping itself. -1 repeats infinitely.
		 */
		public function FlxTimer(duration:Number, repeat:int = -1)
		{
			_duration = _currentDuration = duration;
			_repeat = _currentRepeat = repeat;
 
			_listeners = new Vector.<Function>();
			_listenersParams = new Dictionary();
			_listenerType = new Dictionary();
			_onceListeners = new Dictionary();
 
			_running = false;
 
			FlxG.addTimer(this);
		}
 
		/**
		 * Starts the timer. If the timer is already running, it will be restarted.
		 * 
		 * @return	This FlxTimer instance. Nice for chaining stuff together.
		 */
		public function start():FlxTimer
		{	
			if (_running) 
			{
				stop();
				start();
			}
			else
			{
				_running = true;
				dispatch(START);
			}
 
			return this;
		}
 
		/**
		 * Stops the timer. Stopping the timer resets the time.
		 * 
		 * @return	This FlxTimer instance. Nice for chaining stuff together.
		 */
		public function stop():FlxTimer
		{
			_running = false;
			_currentDuration = _duration;
 
			return this;
		}
 
		/**
		 * Resets the timer and starts it again.
		 * The same effect can be accomplished by calling stop(), then start().
		 * 
		 * @return	This FlxTimer instance. Nice for chaining stuff together.
		 */
		public function reset():FlxTimer
		{
			stop();
			return start();
		}
 
		/**
		 * Pauses the timer. Pausing the timer doesn't reset it, like stop() does.
		 * 
		 * @return	This FlxTimer instance.
		 */
		public function pause():FlxTimer
		{
			_running = false;
 
			return this;
		}
 
		/**
		 * Plays the timer. Playing the timer doesn't reset it, like start() does.
		 * 
		 * @return	This FlxTimer instance.
		 */
		public function play():FlxTimer
		{
			_running = true;
 
			return this;
		}
 
		/**
		 * Registers a listener with this timer. The listener can have any number of parameters registered to it.
		 * 
		 * @param	listener	A function to be called when the timer dispatches
		 * @param	type		What type of function this is i.e. start, complete. Valid values are constants of the FlxTimer class.
		 * @param	params		A set of parameters to be registered to this listener.
		 * 
		 * @return	Whether the listener was successfully added to the timer or not. If the listener had already been added this will return false.
		 */
		public function add(listener:Function, type:int, ...params):Boolean 
		{
			if (_listeners.indexOf(listener) < 0)
			{
				_listeners[_listeners.length] = listener;
				_listenerType[listener] = type;
				return true;
			}
 
			return false;
		}
 
		/**
		 * Registers a listener with this timer. The listener can have any number of parameters registered to it.
		 * This method will automatically remove() the listener from the timer after it has been called once.
		 * 
		 * @param	listener	A function to be called when the timer dispatches.
		 * @param	type		What type of function this is i.e. start, complete. Valid values are constants of the FlxTimer class.
		 * @param	params		A set of parameters to be registered to this listener.
		 * 
		 * @return	Whether the listener was successfully added to the timer or not. If the listener had already been added this will return false.
		 */
		public function addOnce(listener:Function, type:int, ...params):Boolean 
		{
			_onceListeners[listener] = true;
			return add.apply(null, [listener, type].concat(params));
		}
 
		/**
		 * Removes a listener from this timer. It will no longer be called when the timer dispatches.
		 * 
		 * @param	listener	The function to be removed.
		 * 
		 * @return	Whether the listener was successfully removed from the timer or not.
		 */
		public function remove(listener:Function):Boolean 
		{
			var index:int = _listeners.indexOf(listener);
			if (index < 0 || index > _listeners.length)
				return false;
 
			_listeners.splice(index, 1);
			delete _listenersParams[listener];
			delete _listenerType[listener];
			if (_onceListeners[listener])
				delete _onceListeners[listener];
 
			return true;
		}
 
		/**
		 * Removes all listeners from this timer.
		 */
		public function removeAll():void 
		{
			for (var i:int = 0; i < _listeners.length; i++) 
			{
				remove(_listeners[i]);
			}
		}
 
		/**
		 * Updates the timer.
		 */
		public function update():void 
		{
			if (_running) 
			{
				_currentDuration -= FlxG.elapsed;
				if (_currentDuration <= 0) 
				{
					dispatch(COMPLETE);
					_currentDuration = _duration;
				}
			}
		}
 
		/**
		 * Calls all the listeners registered to this timer of the type. Any listeners added through addOnce()
		 * will be removed after this function is called.
		 * 
		 * @param	type	The type of dispatch this is, like start or complete. Valid values are constants of the FlxTimer class.
		 */
		private function dispatch(type:int):void 
		{
			var currentListener:Function;
			var currentListenerParams:Array;
 
			for (var i:int = 0; i < _listeners.length; i++) 
			{
				currentListener = _listeners[i];
				if (_listenerType[currentListener] != type)
					continue;
 
				currentListenerParams = _listenersParams[currentListener];
				currentListener.apply(null, currentListenerParams);
 
				if (_onceListeners[currentListener])
					remove(currentListener);
 
				if (_repeat == -1 || _currentRepeat > 0)
				{
					if (_currentRepeat > 0)
						_currentRepeat--;
				}
				else 
				{
					stop();
				}
			}
		}
 
		/**
		 * Removes this timer from the internal update loop.
		 */
		public function destroy():void
		{
			FlxG.removeTimer(this);
			_listeners = null;
			_listenersParams = null;
			_listenerType = null;
		}
 
	}
 
}